package com.wugh.fast.module.mapper.interceptor;


import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.LRUCache;
import cn.hutool.core.util.PageUtil;
import com.wugh.fast.kernel.core.props.FastProperties;
import com.wugh.fast.module.mapper.CommonMapper;
import com.wugh.fast.module.mapper.EntityInfo;
import com.wugh.fast.module.mapper.interceptor.plugin.CustomizePlugin;
import com.wugh.fast.module.mapper.util.MapperUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.RowBounds;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.weekend.Fn;
import tk.mybatis.mapper.weekend.Weekend;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * 通用Mapper 的增强处理
 *
 * @author WUGH
 * @date 2018/1/31
 */
@Slf4j
@Aspect
public class CommonMapperInterceptor<T> {
    @Autowired
    private FastProperties fastProperties;
    private LRUCache<Class<? extends CommonMapper>, EntityInfo> ENTITY_INFO_CACHE = CacheUtil.newLRUCache(50, 3600 * 1000);
    private ThreadLocal<CommonMapper> mapperThreadLocal = new InheritableThreadLocal<>();
    private ThreadLocal<EntityInfo> entityInfoThreadLocal = new InheritableThreadLocal<>();
    private Map<String, Method> methodMap;

    public CommonMapperInterceptor() {
        methodMap = new ConcurrentHashMap<>();
        Method[] methods = CommonMapper.class.getMethods();
        for (Method method : methods) {
            String methodAutograph = getMethodAutograph(method);
            try {
                Method target = CommonMapperInterceptor.class.getMethod(method.getName(), method.getParameterTypes());
                methodMap.put(methodAutograph, target);
            } catch (NoSuchMethodException e) {
                log.debug("mapper增强拦截器未处理方法: " + getMethodAutograph(method));
            }
        }
    }

    private String getMethodAutograph(Method method) {
        Class<?>[] parameterTypes = method.getParameterTypes();
        StringBuilder methodAutograph = new StringBuilder(method.getName() + "(");
        for (Class<?> parameterType : parameterTypes) {
            if (CommonMapper.class.isAssignableFrom(parameterType)) {
                continue;
            }
            methodAutograph.append(parameterType.getName());
            methodAutograph.append(",");
        }
        if (methodAutograph.charAt(methodAutograph.length() - 1) == ',') {
            methodAutograph.deleteCharAt(methodAutograph.length() - 1);
        }
        methodAutograph.append(")");
        return methodAutograph.toString();
    }

    @Pointcut("target(com.wugh.fast.module.mapper.CommonMapper)")
    public void anyMapper() {
    }

    @Around("anyMapper()")
    public Object dealExample(ProceedingJoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        if (target instanceof CommonMapper) {
            try {
                CommonMapper mapper = (CommonMapper) target;
                this.mapperThreadLocal.set(mapper);
                EntityInfo entityInfo = getEntityInfo(mapper);
                this.entityInfoThreadLocal.set(entityInfo);
                MethodSignature signature = (MethodSignature) joinPoint.getSignature();
                Method method = signature.getMethod();
                String methodAutograph = getMethodAutograph(method);
                if (this.methodMap.containsKey(methodAutograph)) {
                    //有对应的方法,并且有增强插件
                    Method handler = methodMap.get(methodAutograph);
                    Object invoke = handler.invoke(this, joinPoint.getArgs());
                    return invoke;
                }
            } finally {
                this.mapperThreadLocal.remove();
                this.entityInfoThreadLocal.remove();
            }
        }
        return joinPoint.proceed();
    }

    private EntityInfo<T> getEntityInfo(CommonMapper<T> mapper) {
        return ENTITY_INFO_CACHE.get(mapper.getClass(), () -> new EntityInfo(MapperUtils.getEntityClass(mapper), fastProperties.getMapper().getPluginClasses()));
    }

    private CommonMapper<T> getCurrentMapper() {
        return this.mapperThreadLocal.get();
    }

    private EntityInfo<T> getCurrentEntityInfo() {
        return this.entityInfoThreadLocal.get();
    }
    //注解插件的处理

    /**
     * 全部条件过滤才做都通过example,
     * <br/>如 搜索,根据指定条件搜索、指定条件更新、指定条件删除
     *
     * @param example
     */
    void autoHandleExample(Example example) {
        EntityInfo<T> entityInfo = getCurrentEntityInfo();
        entityInfo.getPlugins().stream()
                //判断插件是否在使用
                .filter(CustomizePlugin::isWork)
                .forEach(plugin -> plugin.autoHandleExample(example));
    }

    /**
     * 全部字段更新时候,调用改方法
     *
     * @param record
     */
    void autoHandleUpdate(T record) {
        EntityInfo<T> entityInfo = getCurrentEntityInfo();
        entityInfo.getPlugins().stream()
                //判断插件是否在使用
                .filter(CustomizePlugin::isWork)
                .forEach(plugin -> plugin.autoHandleUpdateRecord(record));
    }

    /**
     * 全字段批量插入时候的处理
     *
     * @param recordList
     */
    void autoHandleInsertList(List<T> recordList) {
        EntityInfo<T> entityInfo = getCurrentEntityInfo();
        entityInfo.getPlugins().stream()
                //判断插件是否在使用
                .filter(CustomizePlugin::isWork)
                .forEach(plugin -> plugin.autoHandleInsertList(recordList));
    }

    /**
     * 全字段插入处理
     *
     * @param record
     */
    void autoHandleInsert(T record) {
        EntityInfo<T> entityInfo = getCurrentEntityInfo();
        entityInfo.getPlugins().stream()
                //判断插件是否在使用
                .filter(CustomizePlugin::isWork)
                .forEach(plugin -> plugin.autoHandleInsert(record));
    }

    /**
     * 不为空字段插入处理
     *
     * @param record
     */
    void autoHandleInsertSelective(T record) {
        EntityInfo<T> entityInfo = getCurrentEntityInfo();
        entityInfo.getPlugins().stream()
                //判断插件是否在使用
                .filter(CustomizePlugin::isWork)
                .forEach(plugin -> plugin.autoHandleInsertSelective(record));
    }


    // 二次封装 Mapper 和 InsertListMapper 的原有方法
    // 主要内容为: 1. 全部操作都通过 Example执行 2.Example处理 3.增加时,自动处理字段 4.删除转移

    public int deleteByPrimaryKey(Object key) {
        Example example = getCurrentEntityInfo().getPKExample(key);
        return deleteByExample(example);
    }

    public int delete(T record) {
        Example example = getCurrentEntityInfo().getRecordExample(record);
        return deleteByExample(example);
    }

    public int insert(T record) {
        autoHandleInsert(record);
        return getCurrentMapper().insert(record);
    }

    public int insertSelective(T record) {
        autoHandleInsertSelective(record);
        return getCurrentMapper().insertSelective(record);
    }

    public int insertList(List<T> recordList) {
        autoHandleInsertList(recordList);
        Integer step = fastProperties.getMapper().getSubInsertListStep();
        CommonMapper<T> currentMapper = getCurrentMapper();
        if (fastProperties.getMapper().getSubInsertList() && recordList != null && recordList.size() > step) {
            //有开启插入sub,并且插入大小大于自动sub的步伐,则进行分页
            AtomicInteger total = new AtomicInteger(0);
            int size = recordList.size();
            int page = PageUtil.totalPage(size, step);
            Stream.iterate(0, i -> i + step).limit(page).forEach(start -> {
                total.addAndGet(currentMapper.insertList(recordList.subList(start, Math.min(start + step, size))));
            });
            return total.get();
        } else {
            return currentMapper.insertList(recordList);
        }
    }

    public boolean existsWithPrimaryKey(Object key) {
        Example example = getCurrentEntityInfo().getPKExample(key);
        return selectCountByExample(example) > 0;
    }


    public List<T> selectAll() {
        Example example = getCurrentEntityInfo().newExample();
        return selectByExample(example);
    }


    public T selectByPrimaryKey(Object key) {
        Example example = getCurrentEntityInfo().getPKExample(key);
        return selectOneByExample(example);
    }


    public int selectCount(T record) {
        Example example = getCurrentEntityInfo().getRecordExample(record);
        return selectCountByExample(example);
    }


    public List<T> select(T record) {
        Example example = getCurrentEntityInfo().getRecordExample(record);
        return selectByExample(example);
    }


    public T selectOne(T record) {
        Example example = getCurrentEntityInfo().getRecordExample(record);
        return selectOneByExample(example);
    }


    public int updateByPrimaryKey(T record) {
        Example example = getCurrentEntityInfo().getPKExampleByRecord(record);
        return updateByExample(record, example);
    }


    public int updateByPrimaryKeySelective(T record) {
        Example example = getCurrentEntityInfo().getPKExampleByRecord(record);
        return updateByExampleSelective(record, example);
    }


    public int deleteByExample(Object example) {
        EntityInfo<T> entityInfo = getCurrentEntityInfo();
        CommonMapper<T> mapper = getCurrentMapper();
        autoHandleExample((Example) example);
        if (entityInfo.hasIsDeleted()) {
            T deleteRecord = entityInfo.getDeletedRecord();
            autoHandleUpdate(deleteRecord);
            return mapper.updateByExampleSelective(deleteRecord, example);
        } else {
            return mapper.deleteByExample(example);
        }
    }


    public List<T> selectByExample(Object example) {
        autoHandleExample((Example) example);
        return getCurrentMapper().selectByExample(example);
    }


    public int selectCountByExample(Object example) {
        autoHandleExample((Example) example);
        return getCurrentMapper().selectCountByExample(example);
    }


    public T selectOneByExample(Object example) {
        autoHandleExample((Example) example);
        return getCurrentMapper().selectOneByExample(example);
    }


    public int updateByExample(T record, Object example) {
        autoHandleExample((Example) example);
        autoHandleUpdate(record);
        return getCurrentMapper().updateByExample(record, example);
    }


    public int updateByExampleSelective(T record, Object example) {
        autoHandleExample((Example) example);
        autoHandleUpdate(record);
        return getCurrentMapper().updateByExampleSelective(record, example);
    }


    public List<T> selectByExampleAndRowBounds(Object example, RowBounds rowBounds) {
        autoHandleExample((Example) example);
        return getCurrentMapper().selectByExampleAndRowBounds(example, rowBounds);
    }


    public List<T> selectByRowBounds(T record, RowBounds rowBounds) {
        Example example = getCurrentEntityInfo().getPKExampleByRecord(record);
        return selectByExampleAndRowBounds(example, rowBounds);
    }

    //以下为增强方法
    public Example getExample() {
        EntityInfo entityInfo = getCurrentEntityInfo();
        return entityInfo.newExample();
    }

    public Weekend<T> getWeekend() {
        EntityInfo<T> entityInfo = getCurrentEntityInfo();
        return entityInfo.newWeekend();
    }


    public <R> Map<R, List<T>> groupMapByExample(Object example, String keyPropertyName) {
        List<T> list = selectByExample(example);
        return groupList(list, keyPropertyName);
    }


    public <R, V> Map<R, List<T>> groupMapByProperty(String propertyName, Collection<V> values, String keyPropertyName) {
        List<T> list = selectByProperty(propertyName, values);
        return groupList(list, keyPropertyName);
    }


    public <R, V> Map<R, List<T>> groupMapByProperty(String propertyName, Collection<V> values) {
        List<T> list = selectByProperty(propertyName, values);
        return groupList(list, propertyName);
    }


    public <R> Map<R, List<T>> groupMapByProperty(String propertyName, Object value, String keyPropertyName) {
        List<T> list = selectByProperty(propertyName, value);
        return groupList(list, keyPropertyName);
    }


    public <R> Map<R, List<T>> groupMapByProperty(String propertyName, Object value) {
        List<T> list = selectByProperty(propertyName, value);
        return groupList(list, propertyName);
    }


    public <R, V> Map<R, List<T>> groupMapByPrimarykey(Collection<V> values, String keyPropertyName) {
        List<T> list = selectByPrimaryKey(values);
        return groupList(list, keyPropertyName);
    }


    public <R> Map<R, T> mapByExample(Object example, String keyPropertyName) {
        List<T> list = selectByExample(example);
        return mapList(list, keyPropertyName);
    }


    public <R> Map<R, T> mapByExample(Object example) {
        List<T> list = selectByExample(example);
        return mapList(list, getCurrentEntityInfo().getPkFieldName());
    }


    public <R, V> Map<R, T> mapByProperty(String propertyName, Collection<V> values, String keyPropertyName) {
        List<T> list = selectByProperty(propertyName, values);
        return mapList(list, keyPropertyName);
    }


    public <R, V> Map<R, T> mapByProperty(String propertyName, Collection<V> values) {
        List<T> list = selectByProperty(propertyName, values);
        return mapList(list, getCurrentEntityInfo().getPkFieldName());
    }


    public <R> Map<R, T> mapByProperty(String propertyName, Object value, String keyPropertyName) {
        List<T> list = selectByProperty(propertyName, value);
        return mapList(list, keyPropertyName);
    }


    public <R> Map<R, T> mapByProperty(String propertyName, Object value) {
        List<T> list = selectByProperty(propertyName, value);
        return mapList(list, getCurrentEntityInfo().getPkFieldName());
    }


    public <R, V> Map<R, T> mapByPrimaryKey(Collection<V> values) {
        List<T> list = selectByPrimaryKey(values);
        return mapList(list, getCurrentEntityInfo().getPkFieldName());
    }


    public <V> int deleteByProperty(String propertyName, Collection<V> values) {
        Example example = getExample();
        example.and().andIn(propertyName, values);
        return deleteByExample(example);
    }


    public int deleteByProperty(String propertyName, Object value) {
        Example example = getExample();
        example.and().andEqualTo(propertyName, value);
        return deleteByExample(example);
    }


    public <V> int deleteByPrimaryKey(Collection<V> values) {
        Example example = getExample();
        example.and().andIn(getCurrentEntityInfo().getPkFieldName(), values);
        return deleteByExample(example);
    }

    public <V> List<T> selectByProperty(String propertyName, Collection<V> values) {
        Example example = getExample();
        example.and().andIn(propertyName, values);
        return selectByExample(example);
    }


    public List<T> selectByProperty(String propertyName, Object value) {
        Example example = getExample();
        example.and().andEqualTo(propertyName, value);
        return selectByExample(example);
    }


    public <V> List<T> selectByPrimaryKey(Collection<V> values) {
        Example example = getExample();
        example.and().andIn(getCurrentEntityInfo().getPkFieldName(), values);
        return selectByExample(example);
    }


    public T selectOneByProperty(String propertyName, Object value) {
        Example example = getExample();
        example.and().andEqualTo(propertyName, value);
        return selectOneByExample(example);
    }

    public int selectCount() {
        return selectCountByExample(getExample());
    }

    public <V> int selectCountByPrimaryKey(V value) {
        Example example = getExample();
        example.and().andEqualTo(getCurrentEntityInfo().getPkFieldName(), value);
        return selectCountByExample(example);
    }

    public <V> int selectCountByPrimaryKey(Collection<V> values) {
        Example example = getExample();
        example.and().andIn(getCurrentEntityInfo().getPkFieldName(), values);
        return selectCountByExample(example);
    }


    public <V> int selectCountByProperty(String propertyName, Collection<V> values) {
        Example example = getExample();
        example.and().andIn(propertyName, values);
        return selectCountByExample(example);
    }


    public int selectCountByProperty(String propertyName, Object value) {
        Example example = getExample();
        example.and().andEqualTo(propertyName, value);
        return selectCountByExample(example);
    }


    public <V, R> int selectCountByProperty(Fn<T, R> propertyGetter, V value) {
        Weekend<T> weekend = getWeekend();
        weekend.weekendCriteria().andEqualTo((Fn<T, Object>) propertyGetter, value);
        return selectCountByExample(weekend);
    }


    public <V, R> int selectCountByProperty(Fn<T, R> propertyGetter, Collection<V> values) {
        Weekend<T> weekend = getWeekend();
        weekend.weekendCriteria().andIn((Fn<T, Object>) propertyGetter, values);
        return selectCountByExample(weekend);
    }


    public <V, R> T selectOneByProperty(Fn<T, R> propertyGetter, V value) {
        Weekend<T> weekend = getWeekend();
        weekend.weekendCriteria().andEqualTo((Fn<T, Object>) propertyGetter, value);
        return selectOneByExample(weekend);
    }


    public <V, R> List<T> selectByProperty(Fn<T, R> propertyGetter, V value) {
        Weekend<T> weekend = getWeekend();
        weekend.weekendCriteria().andEqualTo((Fn<T, Object>) propertyGetter, value);
        return selectByExample(weekend);
    }


    public <V, R> List<T> selectByProperty(Fn<T, R> propertyGetter, Collection<V> values) {
        Weekend<T> weekend = getWeekend();
        weekend.weekendCriteria().andIn((Fn<T, Object>) propertyGetter, values);
        return selectByExample(weekend);
    }


    public <V, R> int deleteByProperty(Fn<T, R> propertyGetter, V value) {
        Weekend<T> weekend = getWeekend();
        weekend.weekendCriteria().andEqualTo((Fn<T, Object>) propertyGetter, value);
        return deleteByExample(weekend);
    }


    public <V, R> int deleteByProperty(Fn<T, R> propertyGetter, Collection<V> values) {
        Weekend<T> weekend = getWeekend();
        weekend.weekendCriteria().andIn((Fn<T, Object>) propertyGetter, values);
        return deleteByExample(weekend);
    }


    public <K, V, R> Map<K, T> mapByProperty(Fn<T, R> propertyGetter, V value) {
        List<T> list = selectByProperty(propertyGetter, value);
        return mapList(list, getCurrentEntityInfo().getPkFieldName());
    }


    public <K, V, R> Map<K, T> mapByProperty(Fn<T, R> propertyGetter, V value, Fn<T, K> keyGetter) {
        List<T> list = selectByProperty(propertyGetter, value);
        return mapList(list, keyGetter);
    }


    public <K, V, R> Map<K, T> mapByProperty(Fn<T, R> propertyGetter, Collection<V> values) {
        List<T> list = selectByProperty(propertyGetter, values);
        return mapList(list, getCurrentEntityInfo().getPkFieldName());
    }


    public <K, V, R> Map<K, T> mapByProperty(Fn<T, R> propertyGetter, Collection<V> values, Fn<T, K> keyGetter) {
        List<T> list = selectByProperty(propertyGetter, values);
        return mapList(list, keyGetter);
    }


    public <K> Map<K, T> mapByExample(Object example, Fn<T, K> keyGetter) {
        List<T> list = selectByExample(example);
        return mapList(list, keyGetter);
    }


    public <K, V> Map<K, List<T>> groupMapByPrimarykey(Collection<V> values, Fn<T, K> keyGetter) {
        List<T> list = selectByPrimaryKey(values);
        return groupList(list, keyGetter);
    }


    public <K, V> Map<K, List<T>> groupMapByProperty(Fn<T, K> propertyGetter, V value) {
        List<T> list = selectByProperty(propertyGetter, value);
        return groupList(list, propertyGetter);
    }


    public <K, V, R> Map<K, List<T>> groupMapByProperty(Fn<T, R> propertyGetter, V value, Fn<T, K> keyGetter) {
        List<T> list = selectByProperty(propertyGetter, value);
        return groupList(list, keyGetter);
    }


    public <K, V> Map<K, List<T>> groupMapByProperty(Fn<T, K> propertyGetter, Collection<V> values) {
        List<T> list = selectByProperty(propertyGetter, values);
        return groupList(list, propertyGetter);
    }


    public <K, V, R> Map<K, List<T>> groupMapByProperty(Fn<T, R> propertyGetter, Collection<V> values, Fn<T, K> keyGetter) {
        List<T> list = selectByProperty(propertyGetter, values);
        return groupList(list, keyGetter);
    }

    public <K> Map<K, List<T>> groupMapByExample(Object example, Fn<T, K> keyGetter) {
        List<T> list = selectByExample(example);
        return groupList(list, keyGetter);
    }

    //增强的私有方法

    /**
     * 使用回调分组集合
     *
     * @param collection   需要分组的集合
     * @param propertyName 分组属性名
     * @param <R>          key类型
     * @return
     */
    private <R> Map<R, List<T>> groupList(List<T> collection, String propertyName) {
        Function<T, R> getter = getCurrentEntityInfo().getPropertyGetter(propertyName);
        return groupList(collection, getter);
    }

    /**
     * 使用回调分组集合
     *
     * @param collection 需要分组的集合
     * @param fun        取key的回调
     * @param <R>        key类型
     * @return
     */
    private <R> Map<R, List<T>> groupList(List<T> collection, Function<T, R> fun) {
        if (collection == null) {
            return new HashMap<>(0);
        }
        return collection.stream().collect(Collectors.groupingBy(fun));
    }

    /**
     * 使用回调映射集合
     *
     * @param collection   需要映射的集合
     * @param propertyName 映射属性名
     * @param <R>          key的类型
     * @return
     */
    private <R> Map<R, T> mapList(List<T> collection, String propertyName) {
        Function<T, R> getter = getCurrentEntityInfo().getPropertyGetter(propertyName);
        return mapList(collection, getter);
    }

    /**
     * 使用回调映射集合
     *
     * @param collection 需要映射的集合
     * @param fun        取key的回调
     * @param <R>        key的类型
     * @return
     */
    private <R> Map<R, T> mapList(List<T> collection, Function<T, R> fun) {
        if (collection == null) {
            return new HashMap<>(0);
        }
        return collection.stream().collect(Collectors.toMap(fun, t -> t, (t1, t2) -> t2));
    }
}
